#include "clipper.hpp"
#include <TGS/GeoMath2.0/PolygonClipper.h>
#include <TGS/GeoMath2.0/Point.h>
#include <TGS/GeoMath2.0/Polygon.h>
#include <TGS/GeoMath2.0/Envelope.h>
#include <assert.h>

namespace
{
	using namespace TGS::GeoMath;

	const double TIMES = 1e9;
	
	ClipperLib::Polygons polygon2ClipperPolygon(const Polygon& polygon)
	{
		ClipperLib::Polygon poly;
		for (size_t i = 0; i < polygon.getPointCount(); ++i)
		{
			const Point& point = polygon.getPoint(i);
			ClipperLib::long64 x = ClipperLib::long64(point.getX() * TIMES);
			ClipperLib::long64 y = ClipperLib::long64(point.getY() * TIMES);
			poly.push_back(ClipperLib::IntPoint(x, y));
		}

		ClipperLib::Polygons polygons;
		polygons.push_back(poly);

		return polygons;
	}
	
	ClipperLib::Polygons polygon2ClipperPolygon(const MultiPolygon& multiPolygon)
	{
		ClipperLib::Polygons polygons;
		for (size_t i = 0; i < multiPolygon.getPolygonCount(); ++i)
		{
			polygons.push_back(polygon2ClipperPolygon(multiPolygon.getPolygon(i))[0]);
		}
		
		return polygons;
	}

	Polygon clipperPolygon2Polygon(const ClipperLib::Polygon& polygon)
	{
		Polygon poly;
		for (size_t i = 0; i < polygon.size(); ++i)
		{
			double x = polygon[i].X / TIMES;
			double y = polygon[i].Y / TIMES;
			poly.appendPoint(x, y);
		}

		return poly;
	}

	MultiPolygon clipperPolygons2MultiPolygon(const ClipperLib::Polygons& polygons)
	{
		MultiPolygon multiPolygon;
		for (size_t i = 0; i < polygons.size(); ++i)
		{
			multiPolygon.addPolygon(clipperPolygon2Polygon(polygons[i]));
		}

		return multiPolygon;
	}
	
	template<typename SubjectPolygonType, typename ClipPolygonType>
	MultiPolygon execute(const SubjectPolygonType& subject, const ClipPolygonType& clip, ClipperLib::ClipType clipType)
	{
		ClipperLib::Clipper clipper;
		clipper.AddPolygons(polygon2ClipperPolygon(subject), ClipperLib::ptSubject);
		clipper.AddPolygons(polygon2ClipperPolygon(clip), ClipperLib::ptClip);
		ClipperLib::Polygons clippedPolys;
		clipper.Execute(clipType, clippedPolys);

		return clipperPolygons2MultiPolygon(clippedPolys);
	}

	MultiPolygon simplifyPolygon(const Polygon& polygon)
	{
		ClipperLib::Polygons result;
		ClipperLib::SimplifyPolygons(polygon2ClipperPolygon(polygon), result);

		return clipperPolygons2MultiPolygon(result);
	}
}


namespace TGS{
namespace GeoMath{

bool PolygonClipper::isIntersect(const Polygon& lhs, const Polygon& rhs)
{
	MultiPolygon result = intersect(lhs, rhs);

	return !result.isEmpty();
}

MultiPolygon PolygonClipper::intersect(const Polygon& subject, const Polygon& clip)
{
	Envelope subEnvelope = subject.getEnvelope();
	Envelope clipEnvelope = clip.getEnvelope();
	if(!subEnvelope.intersect(clipEnvelope)) return MultiPolygon();

	return execute(subject, clip, ClipperLib::ctIntersection);
}

MultiPolygon PolygonClipper::intersect(const Polygon& subject, const MultiPolygon& clip)
{
	return execute(subject, clip, ClipperLib::ctIntersection);
}

MultiPolygon PolygonClipper::unionPolygons(const Polygon& subject, const Polygon& clip)
{
	return execute(subject, clip, ClipperLib::ctUnion);
}

MultiPolygon PolygonClipper::unionPolygons(const Polygon& subject, const MultiPolygon& clip)
{
	return execute(subject, clip, ClipperLib::ctUnion);
}

MultiPolygon PolygonClipper::difference(const Polygon& subject, const Polygon& clip)
{
	return execute(subject, clip, ClipperLib::ctDifference);
}

MultiPolygon PolygonClipper::difference(const Polygon& subject, const MultiPolygon& clip)
{
	return execute(subject, clip, ClipperLib::ctDifference);
}

MultiPolygon PolygonClipper::difference(const MultiPolygon& subject, const MultiPolygon clip)
{
	return execute(subject, clip, ClipperLib::ctDifference);
}

MultiPolygon PolygonClipper::XOR(const Polygon& subject, const Polygon& clip)
{
	return execute(subject, clip, ClipperLib::ctXor);
}

MultiPolygon PolygonClipper::XOR(const Polygon& subject, const MultiPolygon& clip)
{
	return execute(subject, clip, ClipperLib::ctXor);
}

TGS::GeoMath::MultiPolygon PolygonClipper::simplify( const Polygon& polygon )
{
	return simplifyPolygon(polygon);
}

MultiPolygon PolygonClipper::buffer( const Polygon& polygon, double delta )
{
	ClipperLib::Polygons result;
	ClipperLib::OffsetPolygons(polygon2ClipperPolygon(polygon), result, delta * TIMES,
							   ClipperLib::jtMiter);

	return clipperPolygons2MultiPolygon(result);
	
}

}
}